package org.box.demos.multithreading.wgetmt.j16mt;

import java.io.IOException;
import java.rmi.UnexpectedException;
import java.util.Collection;
import java.util.Set;

import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.box.demos.multithreading.wgetmt.Configuration;
import org.box.demos.multithreading.wgetmt.core.ContentParser;
import org.box.demos.multithreading.wgetmt.core.ContentType;
import org.box.demos.multithreading.wgetmt.core.Storage;
import org.box.demos.multithreading.wgetmt.core.URL;

/**
 * <p>
 * Represents a multithreading engine based on Java 1.6 API. The class has just
 * one public method <code>run</code> which kick-off {@link TaskManager} to
 * downloads and collects new urls from received contents, if the recursive mode
 * is on.
 * </p>
 * <p>
 * {@link TaskManager}'s listeners notify User about the progress and save
 * content into the corresponding storage.
 * </p>
 * <p>
 * After download completion, the engine converts all html files to point on
 * local resources, if <code>-rs</code> option is set to <code>true</code> (
 * <code>true</code> is default value)
 * </p>
 * 
 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
 * 
 */
public class J16MTEngine {

	private final HttpClient client = new DefaultHttpClient(
			new ThreadSafeClientConnManager());

	private final ProgressMonitor progressManitor = new ProgressMonitor();

	private final ContentParser parser = new ContentParser();

	private final Configuration cfg;

	private final Storage storage;
	
	private double received;

	/**
	 * Constructs the engine with the given configuration
	 * 
	 * @param cfg
	 * @param storage 
	 */
	public J16MTEngine(Configuration cfg, Storage storage) {
		this.cfg = cfg;
		this.storage = storage;
	}

	/**
	 * <p>
	 * The main methods that runs {@link TaskManager} with url(s) to download.
	 * </p>
	 * 
	 * <p>
	 * {@link TaskManager} collects and downloads new urls from received
	 * contents, if the recursive mode is on.
	 * </p>
	 * <p>
	 * {@link TaskManager}'s listeners notify User about the progress and save
	 * content into the corresponding storage.
	 * </p>
	 * <p>
	 * After download completion, the engine converts all html files to point on
	 * local resources, if <code>-rs</code> option is set to <code>true</code> (
	 * <code>true</code> is default value)
	 * </p>
	 * *
	 * @throws IOException 
	 */
	public void run() throws IOException {

		progressManitor.setShowSkipLog(cfg.showSkipLog());

		TaskManager manager = new TaskManager(client, cfg.getThreads());
		manager.addTaskListener(new TaskManagerListenerImpl(manager, storage));
		manager.addTaskListener(progressManitor);
		
		progressManitor.startDownload();
		manager.requestUrls(Configuration.DEFAULT_URL_LEVEL, cfg.getUrls());
		try {
			storage.connect(cfg);
			//load incomplete urls from storage
			Collection<URL> incompleteUrls = storage.loadIncomplete();
			if (incompleteUrls.size() > 0) {
				System.out.println("Resume model. Incomplate URLs: " + incompleteUrls.size());
				manager.requestUrls(Configuration.DEFAULT_URL_LEVEL, incompleteUrls);
			}
			// run
			manager.runAndWait();
			progressManitor.stopDownload(received);
			// converting links, if specified
			if (cfg.convertLinks()) {
				converLinks(storage);
			}
		} finally {
			if (client != null) {
				client.getConnectionManager().shutdown();
			}
			storage.disconnect();
		}
	}

	/**
	 * <p>
	 * Converts all html files to point on
	 * local resources, if <code>-rs</code> option is set to <code>true</code> (
	 * <code>true</code> is default value)
	 * </p>
	 * 
	 * @param storage - the storage of obtained and saved files
	 * @throws IOException 
	 */
	protected void converLinks(final Storage storage)  {
		// convert urls
		progressManitor.startLinksConvertion();
		try {
			storage.convertLinks(progressManitor);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Since url's groupId represents url's level, pars and returns url's group
	 * 
	 * @param url
	 *            - the downloaded url
	 * 
	 * @return the number of recursive's level of the given url
	 */
	static int getUrlLevel(TaskResult result) {
		return Integer.parseInt(result.getGroupId());
	}

	/**
	 * TaskManagerListenerImpl implements {@link TaskManagerListener} and
	 * provide recursive requesting functionality and storing data to files.
	 * 
	 * @author box
	 * 
	 */
	private class TaskManagerListenerImpl implements TaskManagerListener {

		private static final double MEGABYTE = 1024 * 1024;
		
		private final TaskManager manager;
		private final Storage storage;

		public TaskManagerListenerImpl(TaskManager manager, Storage storage) {
			this.manager = manager;
			this.storage = storage;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.box.demos.multithreading.wgetmt.j16mt.TaskManagerListener#
		 * onTaskCompleted(org.box.demos.multithreading.wgetmt.j16mt.TaskResult,
		 * int)
		 */
		@Override
		public void onTaskCompleted(TaskResult result, int remains) {

			try {
				URL url = result.getUrl();
				byte[] content = result.getContent();
				storage.write(url, content);
				
				received += result.getContent().length / MEGABYTE;
				if (cfg.getDownladLimit() > 0 && cfg.getDownladLimit() <= received) {
					manager.truncateQueue();
					return;
				}
				
				boolean isHtml = url.getContentType() == ContentType.TEXT_HTML;

				int urlLevel = getUrlLevel(result);
				if (isHtml && cfg.isRecursive()) {

					if (urlLevel <= cfg.getMaxLevel()) {

						Set<URL> newUrls = parser.findUrls(
								new String(result.getContent()), url, cfg.getDownloadPattern(), cfg.getSkipPattern());

						newUrls = storage.addUrls(newUrls);
						if (newUrls.size() > 0) {
							manager.requestUrls(String.valueOf(urlLevel + 1),
									newUrls);
						}

					}

				}

			} catch (IOException e) {
				progressManitor.onTaskFailed(result.getGroupId(), result
						.getUrl(), new UnexpectedException(
						"Unable to save the web-page", e));
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.box.demos.multithreading.wgetmt.j16mt.TaskManagerListener#
		 * onTaskFailed(java.lang.String,
		 * org.box.demos.multithreading.wgetmt.core.URL, java.lang.Throwable)
		 */
		@Override
		public void onTaskFailed(String groupId, URL url, Throwable e) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.box.demos.multithreading.wgetmt.j16mt.TaskManagerListener#
		 * onTaskSkipped(org.box.demos.multithreading.wgetmt.j16mt.TaskResult,
		 * int)
		 */
		@Override
		public void onTaskSkipped(TaskResult result, int remains) {
		}

	}
}
